﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace Noris.Tools.TraceVisualiser.Components
{
	#region class Arrow : třída pro uchování dat o šipce a pro podporu jejího vykreslování
    /// <summary>
	/// Arrow : třída pro uchování dat o šipce a pro podporu jejího vykreslování
    /// </summary>
	internal class Arrow
    {
		#region Konstrukce a proměnné
		public Arrow()
		{ }
		public Arrow(PointD begin, PointD end)
		{
			this.Begin = begin;
			this.End = end;
		}
		private PointD _Begin;
		private PointD _End;
		private double _Width;
		private double _WidthArrow;
		private double _ArrowLength;
		private double _ArrowOverhung;
		private double _ArrowSlant;
		private bool _Valid;
		private PointD[] _Arrow;
		private PointF[] _Points;
		private GraphicsPath _Path;
		private PointD _Side1;
		private PointD _Side2;
        private VectorD _Vector;
		#endregion
		#region Property
		/// <summary>
		/// Bod, kde šipka začíná (hranatý konec)
		/// </summary>
		public PointD Begin { get { return this._Begin; } set { this._Begin = value; this._Valid = false; } }
		/// <summary>
		/// Bod, kde šipka končí (špičatý konec)
		/// </summary>
		public PointD End { get { return this._End; } set { this._End = value; this._Valid = false; } }
		/// <summary>
		/// Šířka obdélníkové části čáry v bodě začátku
		/// </summary>
		public double Width { get { return this._Width; } set { this._Width = value; this._CheckParams(); this._Valid = false; } }
		/// <summary>
		/// Změna šířky obdélníkové části čáry v bodě, kde přechází do špice.
		/// 0 = obdélníková část bude skutečně obdélník.
		/// Záporná hodnota: šipka se směrem ke špici bude zužovat.
		/// Kladná hodnota: šipka se směrem ke špici bude rozšiřovat.
		/// </summary>
		public double WidthArrow { get { return this._WidthArrow; } set { this._WidthArrow = value; this._CheckParams(); this._Valid = false; } }
		/// <summary>
		/// Délka špičky, měřená na ose šipky od vrcholu (špice) k bodu, v němž je vztyčena kolmice, na níž končí obdélníková část a přechází do špice
		/// </summary>
		public double ArrowLength { get { return this._ArrowLength; } set { this._ArrowLength = value; this._CheckParams(); this._Valid = false; } }
		/// <summary>
		/// Přesah boků špice nad šířku obdélníkové části
		/// </summary>
		public double ArrowOverhung { get { return this._ArrowOverhung; } set { this._ArrowOverhung = value; this._CheckParams(); this._Valid = false; } }
		/// <summary>
		/// Sklon zadní části špice: 
		/// 0 = zadní část špice bude kolmá
		/// záporné hodnoty = zadní část špice bude směřovat dopředu (špice bude mít tvar tupého nosu)
		/// kladné hodnoty = zadní část špice bude směřovat dozadu ((špice bude mít tvar zpětného háčku)
		/// </summary>
		public double ArrowSlant { get { return this._ArrowSlant; } set { this._ArrowSlant = value; this._CheckParams(); this._Valid = false; } }
		/// <summary>
		/// Bod nejvyššího bočního bodu špice (při orientaci špice doleva je to typicky horní vrchol).
		/// Slouží ke konstrukci LinearGradientBrush.
		/// </summary>
		public PointD Side1 { get { return this._Side1; } }
		/// <summary>
		/// Bod nejnižšího bočního bodu špice (při orientaci špice doleva je to typicky dolní vrchol).
		/// Slouží ke konstrukci LinearGradientBrush.
		/// </summary>
		public PointD Side2 { get { return this._Side2; } }
		/// <summary>
		/// Seznam bodů špice
		/// </summary>
		public PointF[] Points { get { this.CheckValidity(); return _Points; } }
		/// <summary>
		/// GraphicPath popisující špici
		/// </summary>
		public GraphicsPath Path { get { this.CheckValidity(); return _Path; } }
        /// <summary>
        /// Obsahuje vektor své šipky, Begin až End.
        /// Pozor: vektor je struktura.
        /// </summary>
        public VectorD Vector { get { this.CheckValidity(); return _Vector; } }
		#endregion
        #region Kontrola platnosti dat. Tvorba bodů křivky a bodů souvisejících
        /// <summary>
		/// Kontrola platnosti dat, jejich tvorba v případě potřeby (po změně parametrů)
		/// </summary>
		private void CheckValidity()
		{
			if (this._Valid && this._Arrow != null) return;
			this._Arrow = this._CreateArrow();
			this._Points = PointD.ConvertoToPointFArray(this._Arrow);
			this._Path = new GraphicsPath();
			if (this._Points.Length > 0)
				this._Path.AddPolygon(this._Points);
			this._Path.CloseFigure();
		}
		/// <summary>
		/// Zajistí, že parametry budou korektní
		/// </summary>
		private void _CheckParams()
		{
			if (_Width < 1d) _Width = 1d;
			if (_WidthArrow < -_Width) _WidthArrow = -_Width;
			if (_ArrowLength < 0d) _ArrowLength = 0d;
			if (_ArrowOverhung < 0d) _ArrowOverhung = 0d;
			if (_ArrowSlant < -_ArrowLength) _ArrowSlant = -_ArrowLength;
		}
		/// <summary>
		/// Vytvoří a vrátí body šipky podle veškerých zadání
		/// </summary>
		/// <returns></returns>
		private PointD[] _CreateArrow()
		{
			this._CheckParams();

			double width = this._Width;
			double widthArrow = this._WidthArrow;
			double length = this._ArrowLength;
			double overhung = this._ArrowOverhung;
			double slant = this._ArrowSlant;
			PointD pointBegin = this._Begin;
			PointD pointEnd = this._End;
			if (pointBegin == pointEnd) return new PointD[0];

			double lengthTotal = VectorD.GetDistance(pointBegin, pointEnd);
			double widthHalfBegin = width / 2d;
			double widthHalfArrow = widthHalfBegin + (widthArrow / 2d);

            this._Vector = new VectorD(pointBegin, pointEnd);
            VectorD vectorMain = this._Vector;

			PointD? point0 = vectorMain.UprightVectorD.GetPointAtDistance(widthHalfBegin);
			PointD? point6 = vectorMain.DownrightVectorD.GetPointAtDistance(widthHalfBegin);


            if (overhung > 0d)
            {   // Šipka se špicí: na ní existují boční body špice:

                // Místo, kde leží zákoutí šipky:
                double lengthArrow = lengthTotal - (double)length;
                if (lengthArrow < 1d) lengthArrow = 1d;
                PointD? pointArrow = vectorMain.GetPointAtDistance(lengthArrow);
                VectorD vectorArrow = new VectorD(pointArrow.Value, vectorMain.AngleDegr);
                PointD? point1 = vectorArrow.UprightVectorD.GetPointAtDistance(widthHalfArrow);
                PointD? point5 = vectorArrow.DownrightVectorD.GetPointAtDistance(widthHalfArrow);
                
                // Místo, kde leží zadní špice šipky (měřeno od Begin):
                double overHalf = widthHalfArrow + overhung;
                double lengthBack = lengthTotal - (double)length - (double)slant;
                if (lengthBack < 1d) lengthBack = 1d;
                PointD? pointBack = vectorMain.GetPointAtDistance(lengthBack);
                VectorD vectorBack = new VectorD(pointBack.Value, vectorMain.AngleDegr);
                PointD? point2 = vectorBack.UprightVectorD.GetPointAtDistance(overHalf);
                PointD? point4 = vectorBack.DownrightVectorD.GetPointAtDistance(overHalf);

                // Boční body: 
                if (overHalf > widthHalfBegin)
                {   // buď body 2 a 4 (špice), pokud overHalf > widthHalfBegin
                    this._Side1 = point2.Value;
                    this._Side2 = point4.Value;
                }
                else
                {   // anebo body 0 a 6 (základna), když u základny je šipka širší než ve špicích:
                    this._Side1 = point0.Value;
                    this._Side2 = point6.Value;
                }

                return new PointD[] { point0.Value, point1.Value, point2.Value, pointEnd, point4.Value, point5.Value, point6.Value };
            }
            else
            {   // Šipka bez přesahující špice:

                // Místo, kde se láme tělo šipky do špice:
                double lengthBack = lengthTotal - (double)length - (double)slant;
                if (lengthBack < 1d) lengthBack = 1d;
                PointD? pointBack = vectorMain.GetPointAtDistance(lengthBack);
                VectorD vectorBack = new VectorD(pointBack.Value, vectorMain.AngleDegr);
                PointD? point2 = vectorBack.UprightVectorD.GetPointAtDistance(widthHalfArrow);
                PointD? point4 = vectorBack.DownrightVectorD.GetPointAtDistance(widthHalfArrow);

                // Boční body: 
                if (widthHalfArrow > widthHalfBegin)
                {   // buď body 2 a 4 (boky u špice), pokud widthHalfArrow > widthHalfBegin
                    this._Side1 = point2.Value;
                    this._Side2 = point4.Value;
                }
                else
                {   // anebo body 0 a 6 (základna), když u základny je šipka širší než ve špicích:
                    this._Side1 = point0.Value;
                    this._Side2 = point6.Value;
                }

                return new PointD[] { point0.Value, point2.Value, pointEnd, point4.Value, point6.Value };
            }
		}
		#endregion
		#region Tvorba barevné výplně
		/// <summary>
		/// Vrátí LinearGradientBrush, kde barva přechází od Begin ke špici
		/// </summary>
		/// <param name="colorBegin"></param>
		/// <param name="colorEnd"></param>
		/// <returns></returns>
		public LinearGradientBrush GetBrushSlant(Color colorBegin, Color colorEnd)
		{
			LinearGradientBrush lgb = null;
			if (this.Begin != this.End)
				lgb = new LinearGradientBrush(this.Begin, this.End, colorBegin, colorEnd);
			else
				lgb = new LinearGradientBrush(this.Begin, this.End + new PointD(10,0), colorBegin, colorEnd);
			return lgb;
		}
		/// <summary>
		/// Vrátí LinearGradientBrush, kde barva přechází od jednoho boku k druhému, s tím že okrají mají shodnou barvu a střed má barvu jinou
		/// </summary>
		/// <param name="colorOuter"></param>
		/// <param name="colorInner"></param>
		/// <returns></returns>
		public LinearGradientBrush GetBrushLength(Color colorOuter, Color colorInner)
		{
			this.CheckValidity();
			LinearGradientBrush lgb = null;
			if (this._Side1 != this._Side2)
				lgb = new LinearGradientBrush(this._Side1, this._Side2, colorOuter, colorInner);
			else
				lgb = new LinearGradientBrush(this._Side1, this._Side2 + new PointD(10, 0), colorOuter, colorInner);
			// lgb.SetSigmaBellShape(0.3f);
			lgb.SetBlendTriangularShape(0.75f, 0.9f);
			return lgb;
		}
		public PathGradientBrush GetBrushPath(Color colorOuter, Color colorInner, float innerEdge)
		{
			PathGradientBrush pgb = new PathGradientBrush(this.Path);

			pgb.InterpolationColors = new System.Drawing.Drawing2D.ColorBlend(3)
			{
				Positions = new float[] { 0f, innerEdge, 1.0f },
				Colors = new Color[] { colorOuter, colorInner, colorInner }
			};
			return pgb;
		}
		/// <summary>
		/// Připraví grafiku na kreslení šipek (nastaví SmoothingMode atd).
		/// Vrací zazálohovaný předchozí stav grafiky, který je na konci kreslení třeba vrátit: 
		/// <code>
		/// var state = Arrow.PrepareGraphics(graphics);
		/// ... kreslení šipek ...
		/// graphics.Restore(state);
		/// </code>
		/// </summary>
		/// <param name="graphics"></param>
		/// <returns></returns>
		public static GraphicsState PrepareGraphics(Graphics graphics)
		{
			GraphicsState state = graphics.Save();
			graphics.SmoothingMode = SmoothingMode.HighQuality;  // HighSpeed je jako Default = děsné. HighQuality je jako AntiAlias = jde to.
			return state;
		}
		#endregion
	}
	#endregion
    #region struct VectorD : vektor, poloúsečka = vede z bodu Origin směrem Angle
    /// <summary>
    /// VectorD : vektor, poloúsečka = vede z bodu Origin směrem Angle.
    /// Dokáže vypočítat pozici bodu na vektoru, lze vytvořit komplementární pravoúhle orientované vektory.
    /// Používá typy Double, které se používají v goniometrických funkcích (Math.Sin, Cos).
	/// </summary>
    public struct VectorD
    {
        #region Konstruktory
        public VectorD(PointD origin, double angleDegr)
        {
            this._Origin = origin;
            this._Angle = 0d;
            this._SinAngle = 0d;
            this._CosAngle = 1d;
            this._DiffX = 1d;
            this._DiffY = 0d;
            this._Distance = 0d;

            this.AngleDegr = angleDegr;
        }
        public VectorD(PointD origin, PointD target)
        {
            this._Origin = origin;
            this._Angle = 0d;
            this._SinAngle = 0d;
            this._CosAngle = 1d;
            this._DiffX = 1d;
            this._DiffY = 0d;
            this._Distance = 0d;

            this.Target = target;
        }
		public override string ToString()
		{
            if (this.IsEmpty) return "Empty vector";
			return "Origin: " + this.Origin.ToString() + "; Angle: " + this.AngleDegr.ToString() + "°";
		}
        #endregion
        #region Public property
        /// <summary>
        /// Výchozí bod vektoru.
        /// Lze jej změnit: změnou se nezmění úhel.
        /// </summary>
        public PointD Origin
        {
            get { return this._Origin; }
            set { this._Origin = value; }
        }
        /// <summary>
        /// Úhel v radech (0 až 2 × PI).
        /// Lze jej změnit: změnou se nezmění Origin.
        /// </summary>
        public double Angle
        {
            get { return this._Angle; }
            set
            {
                this._Angle = value;
                this._SinAngle = Math.Sin(value);
                this._CosAngle = Math.Cos(value);
                this._DiffX = this._CosAngle;
                this._DiffY = this._SinAngle;
                this._Distance = 100d;
            } 
        }
        /// <summary>
        /// Úhel ve stupních (360°).
        /// Lze jej změnit: změnou se nezmění Origin.
        /// </summary>
        public double AngleDegr
        {
            get { return this._Angle * CoefRadToDegr; }
            set { this.Angle = value / CoefRadToDegr; }
        }
        /// <summary>
        /// Cílový bod vektoru.
        /// Zvláštní property, zapsat lze libovolnou hodnotu s výjimkou hodnoty Origin. Při čtení se vrací bod nikoli zadaný, ale bod dopočítaný.
        /// </summary>
        public PointD? Target
        {
            get { return this.GetPointAtDistance(this._Distance); }
            set
            {
                if (value.HasValue)
                {
                    PointD target = value.Value;
                    this._Angle = GetAngle(this._Origin, target);
                    this._SinAngle = Math.Sin(this._Angle);
                    this._CosAngle = Math.Cos(this._Angle);
                    this._DiffX = target.X - this._Origin.X;
                    this._DiffY = target.Y - this._Origin.Y;
                    this._Distance = GetDistance(this._Origin, target);
                }
            }
        }
        /// <summary>
        /// Příznak prázdného vektoru (který je zadán jako Origin a Target, kde Target = Origin).
        /// </summary>
        public bool IsEmpty
        {
            get { return (this._DiffX == 0d && this._DiffY == 0d); }
        }
        /// <summary>
        /// Vrátí prázdný vektor.
        /// Prázdný vektor má vše nulové a nevrací žádný bod z metod GetPointXxx().
        /// </summary>
        public static VectorD Empty
        {
            get { return new VectorD(); }
        }
        /// <summary>
        /// Vrátí vektor, který je kolmý k tomuto vektoru ve směru proti hodinovým ručičkám (o 3 hodiny dříve)
        /// </summary>
        public VectorD UprightVectorD
        {
            get { if (IsEmpty) return VectorD.Empty; return new VectorD(this.Origin, this.AngleDegr + 90D); }
        }
        /// <summary>
        /// Vrátí vektor, který je kolmý k tomuto vektoru ve směru hodinových ručiček (o 3 hodiny později)
        /// </summary>
        public VectorD DownrightVectorD
        {
            get { if (IsEmpty) return VectorD.Empty; return new VectorD(this.Origin, this.AngleDegr - 90D); }
        }
        /// <summary>
        /// Vrátí vektor, který je protilehlý k tomuto vektoru (o 180°)
        /// </summary>
        public VectorD CounterVectorD
        {
            get { if (IsEmpty) return VectorD.Empty; return new VectorD(this.Origin, this.AngleDegr + 180D); }
        }
        #endregion
        #region Private proměnné
        private static double CoefRadToDegr { get { return 180d / Math.PI; } }
        private PointD _Origin;
        private double _Angle;
        private double _SinAngle;
        private double _CosAngle;
        private double _DiffY;
        private double _DiffX;
        private double _Distance;
        #endregion
        #region Static služby
		/// <summary>
		/// Vrátí úhel (v radech = 2 * PI) z bodu (origin) do bodu (target)
		/// </summary>
		/// <param name="origin"></param>
		/// <param name="target"></param>
		/// <returns></returns>
        public static double GetAngle(PointD origin, PointD target)
        {
            double dx = target.X - origin.X;
            double dy = target.Y - origin.Y;
            return Math.Atan2(dy, dx);
        }
		/// <summary>
		/// Vrátí délku úsečky mezi dvěma danými body.
		/// </summary>
		/// <param name="point1"></param>
		/// <param name="point2"></param>
		/// <returns></returns>
		public static double GetDistance(PointD point1, PointD point2)
		{
			double dx = point2.X - point1.X;
			double dy = point2.Y - point1.Y;
			return Math.Sqrt(dx * dx + dy * dy);
		}
		#endregion
        #region Public metody deskriptivní geometrie
		/// <summary>
		/// Metoda vrátí bod, který leží na vektoru a je v dané vzdálenosti od počátku.
		/// Z důvodu definice vektoru jako poloúsečky je nepřípustné pracovat se zápornou částí poloúsečky,
		/// tedy vstupní hodnota (d) nesmí být záporná. pro zápornou hodnotu je vráceno null.
		/// </summary>
		/// <param name="d"></param>
		/// <returns></returns>
		public PointD? GetPointAtDistance(double d)
		{
            if (this.IsEmpty) return null; 
			if (d < 0d) return null;
			double dx = d * _CosAngle;
			double dy = d * _SinAngle;
			return new PointD(this._Origin.X + dx, this._Origin.Y + dy);
		}
        /// <summary>
        /// Metoda vrátí bod, který leží na vektoru a je na dané souřadnici X.
        /// Pokud pro souřadnici X neexistují žádné body na vektoru, vrátí null.
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public PointD? GetPointAtX(double x)
        {
            if (this.IsEmpty) return null;
            if (this._DiffX == 0d) return null;       // Vektor je rovnoběžný s osou Y: pro takový vektor nelze určit bod pro souřadnici X (buď není žádný, nebo jich je nekonečně mnoho).
            double dx = x - this._Origin.X;           // Vzdálenost souřadnice X od počátku ve směru X (ta může být kladná i záporná)
            double d = dx / this._CosAngle;           // Vzdálenost hledaného bodu od počátku ve směru vektoru (přepona pravoúhlého trojúhelníku, kde známe X a Cos(alfa)
            return GetPointAtDistance(d);             // Bod určíme standardní metodou
        }
        /// <summary>
        /// Metoda vrátí bod, který leží na vektoru a je na dané souřadnici Y.
        /// Pokud pro souřadnici Y neexistují žádné body na vektoru, vrátí null.
        /// </summary>
        /// <param name="y"></param>
        /// <returns></returns>
        public PointD? GetPointAtY(double y)
        {
            if (this.IsEmpty) return null;
            if (this._DiffY == 0d) return null;       // Vektor je rovnoběžný s osou X: pro takový vektor nelze určit bod pro souřadnici Y (buď není žádný, nebo jich je nekonečně mnoho).
            double dy = y - this._Origin.Y;           // Vzdálenost souřadnice Y od počátku ve směru Y (ta může být kladná i záporná)
            double d = dy / this._SinAngle;           // Vzdálenost hledaného bodu od počátku ve směru vektoru (přepona pravoúhlého trojúhelníku, kde známe X a Sin(alfa)
            return GetPointAtDistance(d);             // Bod určíme standardní metodou
        }
        #endregion
    }
	#endregion
    #region struct PointD : definice bodu v rozlišení Double
    /// <summary>
	/// PointD : definice bodu v rozlišení Double.
    /// Tato struktura je implicitně konvertovatelná na PointF a zpět, lze jí tedy použít namísto systémové struktury PointF.
    /// Díky vyšší přesnosti ji lze přímo používat v goniometrických funkcích (Math.Sin, Cos, atd, které pracují s hodnotami Double).
	/// </summary>
	public struct PointD
    {
		#region Konstrukce, proměnné
		public PointD(double x, double y)
		{
			this._X = x;
			this._Y = y;
		}
		public PointD(decimal x, decimal y)
        {
			this._X = (double)x;
			this._Y = (double)y;
		}
        public PointD(float x, float y)
        {
            this._X = (double)x;
            this._Y = (double)y;
        }
		public PointD(int x, int y)
		{
			this._X = (double)x;
			this._Y = (double)y;
		}
		public override int GetHashCode()
		{
			return this._X.GetHashCode() ^ this._Y.GetHashCode();
		}
		public override bool Equals(object obj)
		{
			if (obj is PointD)
			{
				PointD other = (PointD)obj;
				return (this == other);
			}
			return false;
		}
		public override string ToString()
		{
			return "X=" + X.ToString() + "; Y=" + Y.ToString();
		}
		private double _X;
		private double _Y;
		#endregion
		#region Property a implicitní konverze
		/// <summary>
		/// Souřadnice X
		/// </summary>
		public double X { get { return this._X; } set { this._X = value; } }
		/// <summary>
		/// Souřadnice Y
		/// </summary>
		public double Y { get { return this._Y; } set { this._Y = value; } }
        public static implicit operator PointF(PointD value) { return new PointF((float)value.X, (float)value.Y); }
        public static implicit operator PointD(PointF value) { return new PointD(value.X, value.Y); }
		public static implicit operator PointD(Point value) { return new PointD((double)value.X, (double)value.Y); }
		public static PointF[] ConvertoToPointFArray(IEnumerable<PointD> points)
		{
			List<PointF> result = new List<PointF>();
			foreach (PointD point in points)
				result.Add(point);
			return result.ToArray();
		}
		#endregion
		#region Další operátory
		public static PointD operator +(PointD a, PointD b) { return new PointD(a.X + b.X, a.Y + b.Y); }
		public static PointD operator -(PointD a, PointD b) { return new PointD(a.X - b.X, a.Y - b.Y); }
		public static bool operator ==(PointD a, PointD b) { return (a.X == b.X && a.Y == b.Y); }
		public static bool operator !=(PointD a, PointD b) { return (a.X != b.X || a.Y != b.Y); }
		#endregion
	}
	#endregion
}
